/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement the API of class string.
 *
 * This class has the following features: <br>
 * - A same text may be shared by multiple string objects. <br>
 * - The copy-on-write technology is applied when a string object is updated.
 *
 * @author  WXQ#XDU
 * @date    2024.11
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "../include/amcc_string.h"


// Reference :
// Book: The C++Programming Language, Special Edition
//       Section 11.12 A String Class
// Author: Bjarne Stroustrup
// Press:  Addison-Wesley
//


// -----------------------------------------------------------------------
//  Private helpers for implement the class string
//

typedef struct t_string_impl {
    int    refCount; // counter of references to the "text" field
    int    len;      // length of the text
    char * text;     // text body, a C-string
} t_string_impl;

static int string_impl_length(const t_string_impl* pThis) {
    return (NULL == pThis) ? 0 : pThis->len;
}

static bool string_impl_is_empty(const t_string_impl* pThis) {
    return (NULL == pThis) ? true : pThis->len <= 0;
}

// Creates a new string_impl object which holds a C-string.
// And, the length is MIN(LEN(cString), maxLen).
static t_string_impl * string_impl_new(const char * cString, int maxLen) {
    t_string_impl * pThis = (t_string_impl *)malloc( sizeof(t_string_impl) );
    if (NULL == pThis)
        return NULL;
    pThis->refCount = 1;
    if (NULL == cString || '\0' == cString[0] || maxLen < 1) {
        // let me represent an empty string
        pThis->len = 0;   pThis->text = NULL;
        return pThis;
    }

    pThis->len  = maxLen;
    pThis->text = (char*)malloc(pThis->len + 1);
    if (NULL == pThis->text) {
        free(pThis);
        return NULL;
    }

    char * pDest = pThis->text;
    for (int i=0; i < maxLen; ++i) {  // copy up to maxLen chars
        if ('\0' == (*(pDest++) = *(cString++)) ) {
            pThis->len = i;  // the actual length < maxLen
            break;
        }
    }
    pThis->text[pThis->len] = '\0';
    return pThis;
}


static void string_impl_destroy(t_string_impl * pThis) {
    if (NULL == pThis) return;
    free(pThis->text);
    free(pThis);
}

// Return a copy of this object, but return this-self if
// no other t_string using this object.
static t_string_impl * string_impl_clone(t_string_impl * pThis) {
    if (pThis->refCount == 1)
        return pThis;

    pThis->refCount -= 1;
    return string_impl_new(pThis->text, pThis->len);
}

// store a C-string into this object
// return this object on success, or
// return NULL on error and this object keep unchanged
static t_string_impl * string_impl_assign(t_string_impl * pThis, const char* cString, int maxLen) {
    bool  sourceIsEmpty = (NULL == cString || '\0' == cString[0] || maxLen < 1);
    if (sourceIsEmpty) {   // change me to an empty string
        if ( false == string_impl_is_empty(pThis) ) {
            pThis->len = 0;
            free(pThis->text);
            pThis->text = NULL;
        }
        return  pThis;
    }

    // reset text buffer
    char * pDest = (char*)malloc(maxLen + 1);
    if (NULL == pDest) {
        return NULL;
    }

    free(pThis->text);
    pThis->len  = maxLen;
    pThis->text = pDest;
    for (int i=0; i < maxLen; ++i) {  // copy up to maxLen chars
        if ('\0' == (*(pDest++) = *(cString++)) ){
            pThis->len = i;
            break;
        }
    }
    pThis->text[pThis->len] = '\0';
    return  pThis;
}


// -----------------------------------------------------------------------
//  Public operations, API, of class string
//

t_string * string_new(const char * cString, int maxLen) {
    t_string * pThis = (t_string *)malloc( sizeof(t_string) );
    if (NULL == pThis)
        return NULL;

    pThis->impl = string_impl_new(cString, maxLen);
    if (NULL == pThis->impl) {
        free( pThis );
        return NULL;
    }
    return pThis;
}

void string_destroy(t_string * pThis) {
    if (NULL == pThis ) return;
    if (NULL != pThis->impl) {
        -- (pThis->impl->refCount);
        if (pThis->impl->refCount <= 0) {
            string_impl_destroy(pThis->impl);
        }
        pThis->impl = NULL;
    }
    free(pThis);
}

int string_length(const t_string* pThis) {
    return (NULL == pThis) ? 0 : string_impl_length(pThis->impl);
}

bool string_is_empty(const t_string* pThis) {
    return (NULL == pThis) ? true : string_impl_is_empty(pThis->impl);
}

const char * string_text(const t_string * pThis) {
    if (string_is_empty(pThis))
        return "";
    return pThis->impl->text;
}

t_string * string_clone(t_string * pThis) {
    if (NULL == pThis) return NULL;
    t_string * newString = string_new("", 1);
    string_copy(newString, pThis);
    return newString;
}

int string_copy(t_string* pThis, const t_string* pSource) {
    if (NULL == pThis) return 0;
    if (pSource == pThis)
        return string_length(pThis);

    // de-reference to impl-object of this object
    if (NULL != pThis->impl) {
        pThis->impl->refCount -= 1;
        if (pThis->impl->refCount <= 0) {
            string_impl_destroy(pThis->impl);
        }
        pThis->impl = NULL;
    }

    if (NULL != pSource && NULL != pSource->impl)  {
        // reference to impl-object of source
        pThis->impl = pSource->impl;
        pSource->impl->refCount += 1;
    }

    return string_length(pThis);
}


int string_ncopyc(t_string* pThis, const char* cString, int n) {
    if (NULL == pThis) return 0;

    if (NULL == pThis->impl) {
        pThis->impl = string_impl_new(cString, n); // a new impl-object
    } else  if (pThis->impl->refCount == 1) {  // recycle impl-object
        string_impl_assign(pThis->impl, cString, n);
    } else {
        pThis->impl->refCount -= 1;  // de-reference to impl-object
        pThis->impl = string_impl_new(cString, n); // a new impl-object
    }

    return string_impl_length(pThis->impl);
}

int string_copyc(t_string* pThis, const char* cString) {
    return string_ncopyc(pThis, cString, SHRT_MAX);
}


int string_compare(const t_string* pThis, const t_string* pAnother) {
    if (pThis == pAnother)
        return 0;
    if (string_is_empty(pThis))
        return string_is_empty(pAnother) ? 0 : -1;
    if (string_is_empty(pAnother))
        return 1;
    if (pThis->impl == pAnother->impl)
        return 0;

    return strcmp(pThis->impl->text, pAnother->impl->text);
}


int string_comparec(const t_string* pThis, const char* pAnother){
    if(string_is_empty(pThis))
        return (NULL == pAnother || pAnother[0]=='\0') ? 0 : -1;

    if(NULL == pAnother || pAnother[0]=='\0')
        return 1;

    return strcmp(pThis->impl->text, pAnother);
}
